package com.ec.generator.modules.gen.service.impl;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.lang.TypeReference;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.ec.core.common.utils.BeanUtilEx;
import com.ec.core.common.utils.DateUtilEx;
import com.ec.core.common.utils.StringUtilsEx;
import com.ec.core.secure.utils.SecureUtil;
import com.ec.generator.common.enums.DbTypeEnum;
import com.ec.generator.common.enums.TplGenParamEnum;
import com.ec.generator.modules.dynamic.dto.DbTableDTO;
import com.ec.generator.modules.dynamic.dto.QueryTableFieldDTO;
import com.ec.generator.modules.dynamic.dto.TableFieldDTO;
import com.ec.generator.modules.dynamic.service.IDynamicDbService;
import com.ec.generator.modules.gen.dto.*;
import com.ec.generator.modules.gen.entity.Project;
import com.ec.generator.modules.gen.entity.ProjectDb;
import com.ec.generator.modules.gen.entity.Table;
import com.ec.generator.modules.gen.entity.TableColumn;
import com.ec.generator.modules.gen.service.*;
import com.ec.generator.utils.ConvertUtil;
import com.ec.generator.utils.DownLoadFileUtil;
import com.google.common.collect.Maps;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.compress.utils.Lists;
import org.apache.commons.io.IOUtils;
import org.apache.velocity.VelocityContext;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.util.*;
import java.util.regex.Matcher;
import java.util.stream.Collectors;
import java.util.zip.ZipOutputStream;

/**
 * @description:
 * @author: Eric Lee
 * @time: 2022/1/27 10:22
 */
@Slf4j
@Service
public class GenCodeServiceImpl implements IGenCodeService {
    @Resource
    private IProjectService projectService;

    @Resource
    private IProjectDbService projectDbService;

    @Resource
    private IDynamicDbService dynamicDbService;

    @Resource
    private ITableService tableService;

    @Resource
    private ITableColumnService tableColumnService;

    @Override
    public void genScaffold(Long projectId, HttpServletResponse response) {
        GenScaffoldParamDTO genScaffoldParamDTO = GenScaffoldParamDTO.builder()
                .genDate(DateUtilEx.dateFormat(new Date()))
                .author(SecureUtil.userAccount())
                .build();
        Project project = projectService.getById(projectId);
        BeanUtilEx.copyNotNull(project, genScaffoldParamDTO);
        LambdaQueryWrapper<ProjectDb> queryWrapper = Wrappers.lambdaQuery();
        queryWrapper.in(ProjectDb::getProjectId, projectId);
        queryWrapper.eq(ProjectDb::getIsDelete, 0);
        List<ProjectDb> dbEntities = projectDbService.list(queryWrapper);
        List<DbParamDTO> dbList = Lists.newArrayList();
        initDb(dbList, dbEntities);
        dbList.sort(Comparator.comparing(DbParamDTO::getDbPrimary).reversed());
        genScaffoldParamDTO.setDbList(dbList);
        genScaffoldParamDTO.setContextPath(genScaffoldParamDTO.getContextPath().startsWith("/")
                ? genScaffoldParamDTO.getContextPath() : "/" + genScaffoldParamDTO.getContextPath());
        genScaffoldParamDTO.setStartupName(ConvertUtil.formatClassName(genScaffoldParamDTO.getProjectName()));
        downloadWithZip(genScaffoldParamDTO, 1, genScaffoldParamDTO.getArtifactId(), response);
    }

    private void downloadWithZip(Object paramDTO, Integer type, String zipName, HttpServletResponse response) {
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        ZipOutputStream zipOutputStream = new ZipOutputStream(outputStream);
        Map<String, String> replaceValue = Maps.newHashMap();
        if (type == 1) {
            GenScaffoldParamDTO genScaffoldParamDTO = (GenScaffoldParamDTO) paramDTO;
            replaceValue.put("startupName", genScaffoldParamDTO.getStartupName());
            replaceValue.put("packageName", genScaffoldParamDTO.getPackageName());
            renderTpl(zipOutputStream, paramDTO, type, replaceValue);
        } else if (type == 2) {
            List<GenTableFunDTO> genTableFunDTOS = Convert.convert(new TypeReference<List<GenTableFunDTO>>() {
            }, paramDTO);
            genTableFunDTOS.forEach(e -> {
                replaceValue.put("packageName", e.getPackageName());
                replaceValue.put("moduleName", e.getModuleName());
                replaceValue.put("className", e.getClassName());
                renderTpl(zipOutputStream, e, type, replaceValue);
            });
        }
        IOUtils.closeQuietly(zipOutputStream);
        outputStream.toByteArray();
        try {
            DownLoadFileUtil.DownloadGenCode(response, outputStream.toByteArray(), zipName);
        } catch (Exception e) {
            log.error("下载自动生成代码失败。{}",e);
        }
    }


    /**
     * 渲染模板
     */
    private void renderTpl(ZipOutputStream zipOutputStream, Object paramDTO, Integer type, Map<String, String> replaceMap) {
        DownLoadFileUtil.initVelocity();
        VelocityContext velocityContext = new VelocityContext(BeanUtilEx.beanToMap(paramDTO));
        for (Map.Entry<String, File> entry : TplGenParamEnum.TemplateTypeEnum.getByType(type).getTplFilePathMap().entrySet()) {
            // 文件名包含路径
            String filePathName = entry.getKey();
            File file = entry.getValue();
            String fileBaseName;
            fileBaseName = replaceFileNameParam(filePathName, replaceMap);
            DownLoadFileUtil.LoadZipOutputStream(velocityContext,
                    file,
                    fileBaseName,
                    zipOutputStream);
        }
    }

    private String replaceFileNameParam(String replaceTpl, Map<String, String> replaceMap) {
        String packageName = replaceMap.get("packageName").replaceAll("\\.", Matcher.quoteReplacement(File.separator));
        replaceMap.put("packageName", packageName);
        return StringUtilsEx.removeEnd(StringUtilsEx.strSubReplace(replaceMap, replaceTpl), ".vm");
    }

    private void initDb(List<DbParamDTO> dbList, List<ProjectDb> dbEntities) {
        dbEntities.forEach(e -> {
            Map<String, String> replaceValue = Maps.newHashMap();
            replaceValue.put("dbIpPort", e.getDbIp() + ":" + e.getDbPort());
            replaceValue.put("dbName", e.getDbName());
            DbParamDTO dbParamDTO = DbParamDTO.builder()
                    .dbPrimary(e.getDbPrimary())
                    .userName(e.getDbUser())
                    .password(e.getDbPassword())
                    .dbName(ConvertUtil.formatName(e.getDbName()))
                    .dbUrl(StringUtilsEx.strSubReplace(replaceValue, DbTypeEnum.MYSQL.getUrl()))
                    .build();
            dbList.add(dbParamDTO);
        });
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void genFun(GenFuncDTO genFuncDTO, HttpServletResponse response) {
        saveTableField(genFuncDTO);
        String createTime = DateUtilEx.dateFormat(new Date());
        String author = SecureUtil.userAccount();
        Project project = projectService.getById(genFuncDTO.getProjectId());
        List<Table> tableEntities = queryTables(genFuncDTO);
        List<TableColumn> tableColumnEntities = tableColumnService.list(Wrappers.<TableColumn>lambdaQuery()
                .in(TableColumn::getTableId,
                        tableEntities.stream().map(Table::getTableId).collect(Collectors.toList())));
        Map<Long, List<TableColumn>> tableColumnMap = tableColumnEntities.stream().collect(Collectors.groupingBy(TableColumn::getTableId));
        List<GenTableFunDTO> genTableFunDTOS = tableEntities.stream().map(e -> {
            GenTableFunDTO genTableFunDTO = GenTableFunDTO.builder()
                    .genDate(createTime).author(author).packageName(project.getPackageName())
                    .build();
            genTableFunDTO.setClassName(e.getClassName());
            genTableFunDTO.setModuleName(e.getModuleName());
            genTableFunDTO.setClassAlias(ConvertUtil.firstLowerCase(e.getClassName()));
            genTableFunDTO.setTableName(e.getTableName());
            genTableFunDTO.setColumnList(BeanUtilEx.copyList(tableColumnMap.get(e.getTableId()), TableColumnDTO.class));
            return genTableFunDTO;
        }).collect(Collectors.toList());
        downloadWithZip(genTableFunDTOS, 2, project.getArtifactId(), response);
    }

    private void saveTableField(GenFuncDTO genFuncDTO) {
        List<DbTableDTO> tables = filterTable(genFuncDTO);
        if (!CollectionUtil.isEmpty(tables)) {
            QueryTableFieldDTO queryTableFieldDTO = QueryTableFieldDTO.builder()
                    .tableNames(tables.stream().map(DbTableDTO::getTableName).collect(Collectors.toList()))
                    .dbId(genFuncDTO.getDbId())
                    .dbName(genFuncDTO.getDbName())
                    .build();
            List<TableFieldDTO> tableFieldDTOS = dynamicDbService.queryTableFields(queryTableFieldDTO);
            List<Table> tableEntities = buildTableInfo(tables, genFuncDTO.getDbId());
            tableService.saveBatch(tableEntities);
            tableEntities.forEach(e -> {
                tableFieldDTOS.forEach(v -> {
                    if (e.getTableName().equals(v.getTableName())) {
                        v.setTableId(e.getTableId());
                    }
                });
            });
            List<TableColumn> tableColumnEntities = buildTableColumn(tableFieldDTOS);
            tableColumnService.saveBatch(tableColumnEntities);
        }
    }


    private List<Table> queryTables(GenFuncDTO genFuncDTO) {
        List<String> tableNames = genFuncDTO.getTableList().stream().map(DbTableDTO::getTableName).collect(Collectors.toList());
        LambdaQueryWrapper<Table> tableQueryWrapper = Wrappers.lambdaQuery();
        tableQueryWrapper.eq(Table::getDbId, genFuncDTO.getDbId());
        tableQueryWrapper.in(Table::getTableName, tableNames);
        return tableService.list(tableQueryWrapper);
    }

    /**
     * // 过滤出已经存在的表信息，
     *
     * @param genFuncDTO
     * @return
     */
    private List<DbTableDTO> filterTable(GenFuncDTO genFuncDTO) {
        List<String> tableEntities = queryTables(genFuncDTO).stream().map(Table::getTableName).collect(Collectors.toList());
        return genFuncDTO.getTableList().stream().filter(e -> !tableEntities.contains(e.getTableName())).collect(Collectors.toList());
    }

    private List<Table> buildTableInfo(List<DbTableDTO> tables, Long dbId) {
        if (!CollectionUtil.isEmpty(tables)) {
            return tables.stream().map(e -> {
                Table table = new Table();
                table.setDbId(dbId);
                table.setTableComment(e.getTableComment());
                table.setTableName(e.getTableName());
                table.setClassName(ConvertUtil.formatClassName(e.getTableName()));
                table.setModuleName(e.getTableName().replaceAll("_", ""));
                return table;
            }).collect(Collectors.toList());
        }
        return Lists.newArrayList();
    }

    private List<TableColumn> buildTableColumn(List<TableFieldDTO> tableFieldDTOS) {
        if (!CollectionUtil.isEmpty(tableFieldDTOS)) {
            return tableFieldDTOS.stream().map(e -> {
                TableColumn tableColumn = new TableColumn();
                tableColumn.setTableId(e.getTableId());
                tableColumn.setColumnComment(e.getColumnComment());
                tableColumn.setColumnName(e.getColumnName());
                tableColumn.setColumnType(e.getDataType());
                tableColumn.setIfPk("PRI".equals(e.getColumnKey()) ? 1 : 0);
                tableColumn.setIfIncrement("PRI".equals(e.getColumnKey())
                        && StringUtilsEx.isNotBlank(e.getExtra())
                        && "auto_increment".equals(e.getExtra().toLowerCase()) ? 1 : 0);
                tableColumn.setJavaType(ConvertUtil.toJavaType(e.getDataType()));
                tableColumn.setJavaField(ConvertUtil.formatFieldName(e.getColumnName()));
                return tableColumn;
            }).collect(Collectors.toList());
        }
        return Lists.newArrayList();
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean syncField(SyncFieldDTO syncFieldDTO) {
        QueryTableFieldDTO queryTableFieldDTO = QueryTableFieldDTO.builder()
                .tableNames(Arrays.asList(syncFieldDTO.getTableName()))
                .dbId(syncFieldDTO.getDbId())
                .dbName(syncFieldDTO.getDbName())
                .build();
        List<TableFieldDTO> tableFieldDTOS = dynamicDbService.queryTableFields(queryTableFieldDTO);
        tableFieldDTOS.forEach(e -> {
            e.setTableId(syncFieldDTO.getTableId());
        });
        List<TableColumn> tableColumnEntities = buildTableColumn(tableFieldDTOS);
        tableColumnService.getBaseMapper().delete(Wrappers.<TableColumn>lambdaQuery().eq(TableColumn::getTableId, syncFieldDTO.getTableId()));
        tableColumnService.saveBatch(tableColumnEntities);
        return true;
    }
}
